package com.example.version.controll.util;

import com.example.version.controll.model.RepositoryConfig;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.commons.collections4.CollectionUtils;
import org.eclipse.jgit.api.*;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.transport.*;
import org.jboss.logging.Logger;
import org.springframework.util.StopWatch;
import org.springframework.util.StringUtils;

import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;

/**
 * git操作工具类
 */
@Data
@NoArgsConstructor
public class GitUtil {
    private static Logger logger = Logger.getLogger(GitUtil.class);
    private Git git;
    private CredentialsProvider usernamePasswordCredentialsProvider;

    private RepositoryConfig repositoryConfig;

    public GitUtil(RepositoryConfig repositoryConfig) throws GitAPIException {
        this.usernamePasswordCredentialsProvider = getAuth();
        this.repositoryConfig = repositoryConfig;
        String repository = PathManagerUtil.gitDataPath + repositoryConfig.getName();
        File file = new File(repository);
        if (!file.exists()) {
            clone(repositoryConfig.getGitUrl(), repository);
        }
        this.git = init(repository);
    }

    public void createPushTag(String branch, String tag) throws GitAPIException, IOException {
        createTag(tag, branch);
        pushTag();
    }

    public void batchCommit(String version, String... path) {
        try {
            System.out.println();
            System.out.println();
            logger.info("提交--" + repositoryConfig.getBranch());
            for (String s : path) {
                logger.info("add " + s);
                add(s);
            }
//            checkout(repositoryConfig.getBranch());
//            pull(repositoryConfig.getBranch());
            commit(version);
            push(repositoryConfig.getBranch());
            System.out.println();
            System.out.println();
        } catch (Exception e) {
            logger.error("ERROR ----------" + repositoryConfig.getName() + "：仓库提交失败" + e.getMessage());
        }
    }

    public void batchUpdate() {
        try {
            System.out.println();
            System.out.println();
            logger.info("更新--" + repositoryConfig.getBranch());
            reset();
            checkout(repositoryConfig.getBranch());
            pull(repositoryConfig.getBranch());
            System.out.println();
            System.out.println();
        } catch (Exception e) {
            logger.error("ERROR ----------" + repositoryConfig.getName() + "：仓库更新失败" + e.getMessage());
        }
    }

    public Git init (String path) throws GitAPIException {
//        logger.info(repositoryConfig.getName() + "--git init " + path);
        return Git.init().setDirectory(new File(path)).call();
    }

    public void checkout(String branch) throws GitAPIException {
//        logger.info(repositoryConfig.getName() + "--git checkout " + branch);
        List<String> localBranchList = getLocalBranchList();
        CheckoutCommand checkoutCommand = git.checkout().setName(branch);
        boolean equals = false;
        for (String s : localBranchList) {
            if (s.equals(branch)) {
                equals = true;
            }
        }
        // 如果本地没有该分支 则从远端获取
        if (!equals) {
            checkoutCommand.setCreateBranch(true);
        }
        checkoutCommand.call();
    }

    /**
     * 获取本地分支列表
     * @return
     * @throws GitAPIException
     */
    public List<String> getLocalBranchList() throws GitAPIException {
        return git.branchList().call().stream().map(Ref::getName).filter(x -> x.startsWith("refs/heads/")).map(x -> x.replace("refs/heads/", "")).collect(Collectors.toList());
    }

    /**
     * 获取远端分支列表
     * @param url
     * @return
     * @throws GitAPIException
     */
    public static List<String> getRemoteBranchs(String url) throws GitAPIException {
        return Git
                .lsRemoteRepository()
                .setRemote(url)
                .setCredentialsProvider(getAuth())
                .call()
                .stream()
                .map(Ref::getName)
                .filter(x -> x.startsWith("refs/heads/"))
                .map(x -> x.replace("refs/heads/", ""))
                .collect(Collectors.toList());
    }

    /**
     * pull
     * @param branch
     * @throws GitAPIException
     */
    public void pull(String branch) throws GitAPIException {
//        logger.info(repositoryConfig.getName() + "--git pull " + branch);
        git.pull().setRemoteBranchName(branch).setCredentialsProvider(getAuth()).call();
    }

    /**
     * commit
     * @param message
     * @throws GitAPIException
     */
    public void commit(String message) throws GitAPIException {
//        logger.info(repositoryConfig.getName() + "--git commit " + message);
        git.commit().setMessage(message).call();
    }

    /**
     * add filePath 提供相对路径
     * @param filePath
     * @throws GitAPIException
     */
    public void add(String filePath) throws GitAPIException {
//        logger.info(repositoryConfig.getName() + "--git add file " + filePath);
        git.add().addFilepattern(filePath).call();
    }

    /**
     * 重置缓冲区  强制更新远端代码至本地时配合使用
     * @throws GitAPIException
     */
    public void reset() throws GitAPIException {
//        logger.info(repositoryConfig.getName() + "--git reset ");
        git.reset().setMode(ResetCommand.ResetType.HARD).call();
    }

    /**
     * push
     * @param branch
     * @throws GitAPIException
     */
    public void push(String branch) throws GitAPIException {
//        logger.info(repositoryConfig.getName() + "--git push " + branch);
        git.push().setRemote("origin").setRefSpecs(new RefSpec(branch)).setCredentialsProvider(getAuth()).call();
    }

    /**
     * 创建分支
     * @param branch
     * @throws GitAPIException
     */
    public void createBranch(String branch) throws GitAPIException {
//        logger.info(repositoryConfig.getName() + "--git createBranch " + branch);
        git.branchCreate().setName(branch).call();
    }

    /**
     * 合并分支
     * @param source
     * @param target
     * @throws GitAPIException
     */
    public void merge(String source, String target) throws GitAPIException {
        Ref refdev = git.checkout().setName(source).call(); //切换分支获取分支信息存入Ref对象里
        git.checkout().setName(target).call();   //切换回master分支
        git.merge().include(refdev)  // 合并目标分支
                .setCommit(true)           //同时提交
                .setFastForward(MergeCommand.FastForwardMode.NO_FF)// 分支合并策略NO_FF代表普通合并
                .setMessage(target + " Merge " + source)     //设置提交信息   //  FF代表快速合并
                .call();
    }

    /**
     * 账号密码
     * @return
     */
    public static CredentialsProvider getAuth() {
        return new UsernamePasswordCredentialsProvider(FileUtil.configDTO.getUserName(), FileUtil.configDTO.getPassWord());
    }

    /**
     * 克隆
     * @param remotePath
     * @param filePath
     * @throws GitAPIException
     */
    public void clone(String remotePath, String filePath) throws GitAPIException {
//        logger.info(repositoryConfig.getName() + "--git clone " + remotePath + " to " + filePath);
        File localpath = new File(filePath);
        Git git = Git.cloneRepository()
                .setURI(remotePath)   //设置git远端URL
                .setDirectory(localpath)  //设置本地仓库位置
                .setCredentialsProvider(getAuth())   //设置身份验证
                .setCloneSubmodules(true)    //设置是否克隆子仓库
//                .setBranch(branch)  //设置克隆分支
                .call();   //启动命令
        git.getRepository().close();
        git.close();     //关闭源，以释放本地仓库锁
    }

    /**
     * 创建tag  自动查找指定 branch的最新commit 基于该commit创建tag
     * @param tag
     * @param branch
     * @throws GitAPIException
     * @throws IOException
     */
    public void createTag(String tag, String branch) throws GitAPIException, IOException {
        List<String> remoteBranchList = getRemoteBranchs(repositoryConfig.getGitUrl());
        List<Ref> allTags = getAllTags();
        for (Ref allTag : allTags) {
            if (allTag.getName().contains(tag)) {
                logger.error("tag已存在");
                return;
            }
        }
        for (String s : remoteBranchList) {
            if (s.equals(branch)) {
                RevCommit newCommitId = getNewCommitId(branch);
                git.tag().setObjectId(newCommitId).setName(tag).setMessage(tag).call();
                return;
            }
        }
        logger.error("基于" + branch + "分支创建tag失败：" + tag + ", 未找到指定分支");
    }

    /**
     * 推送本地tag到远端
     * @throws GitAPIException
     */
    public void pushTag() throws GitAPIException {
        git.push().setRemote("origin").setPushTags().setCredentialsProvider(getAuth()).call();
    }

    /**
     * 获取所有tag
     * @return
     * @throws GitAPIException
     * @throws IOException
     */
    public List<Ref> getAllTags() throws GitAPIException, IOException {
        git.fetch().setRemote("origin").setTagOpt(TagOpt.FETCH_TAGS).setCredentialsProvider(getAuth()).call();
        List<Ref> call = git.tagList().call();
        String path = git.getRepository().getDirectory().getPath();
        System.out.println(path);
        List<Ref> collect = call.stream()
                .sorted((c1, c2) -> {
                    ObjectId objectId1 = c1.getObjectId();
                    ObjectId objectId2 = c2.getObjectId();
                    RevWalk revWalk = new RevWalk(git.getRepository());
                    try {
                        RevCommit commit1 = revWalk.parseCommit(objectId1);
                        RevCommit commit2 = revWalk.parseCommit(objectId2);
                        Date when1 = commit1.getAuthorIdent().getWhen();
                        Date when2 = commit2.getAuthorIdent().getWhen();
                        return when1.compareTo(when2);
                    } catch (IOException e) {
                        return -1;
                    }
                })
                .collect(Collectors.toList());
        for (Ref ref : collect) {
            System.out.println(ref.getName() + " " + new File(path + File.separator + ref.getName()).lastModified());
        }
        return collect;
    }

    /**
     * 删除远端tag
     * @param tags
     * @throws GitAPIException
     * @throws IOException
     */
    public void delTag(String... tags) throws GitAPIException, IOException {
        List<Ref> allTags = getAllTags();
        List<String> delTags = new ArrayList<>();
        List<String> delRemoteTags = new ArrayList<>();
        Set<String> collect = allTags.stream().map(Ref::getName).collect(Collectors.toSet());
        for (String tag : tags) {
            if (collect.contains("refs/tags/" + tag)) {
                delTags.add(tag);

                delRemoteTags.add("refs/tags/" + tag);
            } else {
                logger.error("分支不存在:" + tag);
            }
        }
        if (CollectionUtils.isNotEmpty(delTags)) {
            // 删除本地tag
            git.tagDelete().setTags(delTags.stream().toArray(String[]::new)).call();
            // 删除远端tag
            PushCommand pushCommand = git.push().setCredentialsProvider(getAuth());
            for (String delRemoteTag : delRemoteTags) {
                pushCommand.add(":" + delRemoteTag);
            }
            pushCommand.setRemote("origin").setCredentialsProvider(getAuth()).setPushTags().call();
        }
    }

    public RevCommit getNewCommitId(String branchName) throws IOException, GitAPIException {
        Iterable<RevCommit> commits = git.log().all().call();
        RevWalk revCommits = new RevWalk(git.getRepository());
        for (RevCommit commit : commits) {
            RevCommit revCommit = revCommits.parseCommit(commit.getId());
            for (Map.Entry<String, Ref> stringRefEntry : git.getRepository().getAllRefs().entrySet()) {
                if (stringRefEntry.getKey().startsWith("refs/remotes/origin")) {
                    if (revCommits.isMergedInto(revCommit, revCommits.parseCommit(stringRefEntry.getValue().getObjectId()))) {
                        String name = stringRefEntry.getValue().getTarget().getName();
                        if (name.contains(branchName)) {
                            return commit;
                        }
                    }
                }
            }
        }
        return null;
    }

    public List<RevCommit> getCommits(String branchName) throws IOException, GitAPIException {
        Iterable<RevCommit> commits = git.log().all().call();
        RevWalk revCommits = new RevWalk(git.getRepository());
        List<RevCommit> retCommits = new ArrayList<>();
        for (RevCommit commit : commits) {
            RevCommit revCommit = revCommits.parseCommit(commit.getId());
            for (Map.Entry<String, Ref> stringRefEntry : git.getRepository().getAllRefs().entrySet()) {
                if (stringRefEntry.getKey().startsWith("refs/remotes/origin")) {
                    if (revCommits.isMergedInto(revCommit, revCommits.parseCommit(stringRefEntry.getValue().getObjectId()))) {
                        String name = stringRefEntry.getValue().getTarget().getName();
                        if (name.contains(branchName)) {
                            retCommits.add(commit);
                        }
                    }
                }
            }
        }
        return retCommits;
    }

}
